package org.xs.web.http;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.Method;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.sojo.interchange.json.JsonSerializer;

import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.io.VelocityWriter;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.xs.web.http.provider.servlet.HTTPServletContext;
import org.xs.web.http.provider.servlet.HTTPServletRequest;
import org.xs.web.http.provider.servlet.HTTPServletResponse;
import org.xs.web.http.provider.servlet.HTTPServletSession;
import org.xs.web.http.velocity.ChainedContext;
import org.xs.web.http.velocity.LayoutContext;
import org.xs.web.http.velocity.VelocityConfigurer;
import org.xs.web.util.Reflections;
 
public class XSServlet extends HttpServlet{
	public static final Logger log = Logger.getLogger(XSServlet.class);
	private static final long serialVersionUID = 1L;
	private VelocityEngine velocityEngine;
	private static final String DEFAULT_ENCODE="UTF-8";
	private ApplicationContext appContext;

	@Override
	public void service(ServletRequest req, ServletResponse res)throws ServletException,IOException{
		if(!(req instanceof HttpServletRequest) || !(res instanceof HttpServletResponse)){
			return;
		}
		req.setCharacterEncoding("UTF-8");
		res.setCharacterEncoding("UTF-8");
		HTTPServletContext ctx = new HTTPServletContext(getServletContext());
		HTTPServletRequest httpReq = new HTTPServletRequest(ctx, (HttpServletRequest)req,new HTTPServletSession(((HttpServletRequest)req).getSession(true)));
		HTTPServletResponse httpResp = new HTTPServletResponse((HttpServletResponse)res);
		S.init(this,httpReq, httpResp,ctx,getApplicationContext());
		String[] parts = httpReq.getDelegate().getServletPath().replaceAll("^/|\\.\\w+$", "").split("/");
		
		String[] beanAndMethod = getBeanAndMethod(parts);
		ApplicationContext context = getApplicationContext();
		Object bean = null;
		try {
			 bean = context.getBean(beanAndMethod[0]);
		} catch (Exception e) {
			
		}
		if(bean == null){
			onceWrite(httpResp.getDelegate(),"Can not be found bean :" +beanAndMethod[0]);
			return;
		}
		if(!(bean instanceof ActionController)){
			String msg = "Find bean [%s] in context but bean must is instance [ActionController]";
			onceWrite(httpResp.getDelegate(),String.format(msg, beanAndMethod[0]));
			return;
		}
		Method method = Reflections.getMethodAndWrap(beanAndMethod[1], bean.getClass());
		if(method == null){
			onceWrite(httpResp.getDelegate(),"Can not be find method :" +beanAndMethod[1] +" in bean : "+beanAndMethod[0]);
			return;
		}
		if(method.getGenericParameterTypes().length > 0){
			String msg ="Find bean [%s] in context but method must not has parameters and return type!";
			onceWrite(httpResp.getDelegate(),String.format(msg, beanAndMethod[0]));
			return;
		}
		 
		//boolean isJson = method.getAnnotation(JSON.class) == null ? false : true;
		boolean isJson = false;
		try {
			Reflections.invoke(method, bean);
		} catch (Exception e) {
			String msg = "Find bean [%s] in context but invoke method [%s] occuring error: %s";
			onceWrite(httpResp.getDelegate(),String.format(msg, beanAndMethod[0],beanAndMethod[1],e.getMessage()));
			return ;
		}
		if(isJson){
			JsonSerializer js = new JsonSerializer();
			Object result = js.serialize(S.getContext());
			onceWrite(httpResp.getDelegate(),(String)result);
			return;
		}
		String templateName = getTemplateName(beanAndMethod);
		try {
			render(httpResp.getDelegate(),templateName, DEFAULT_ENCODE);	
		} catch (Exception e) {
			if(e instanceof TemplateNotFoundException){
				onceWrite(httpResp.getDelegate(), String.format("Can not be found template with path [%s]",new Object[]{templateName}));
			}
			throw new IOException(e);
		}finally{
			S.shutdown(this);
		}
		
	}

	protected String getTemplateName(String[] beanAndMethod){
		return beanAndMethod[0] +"/" + beanAndMethod[1] + XSRules.templateDot();
	} 

	public String[] getBeanAndMethod(String[] parts) {
		String beanName = null;
		String methodName = null;
		if(parts.length == 1){
			beanName = parts[0];
		}else{
			StringBuilder sb = new StringBuilder();
			for(int i=0;i< parts.length -1 ;i++){
				sb.append(parts[i]).append("/");
			}
			 beanName = sb.substring(0, sb.length() -1);
			 methodName = parts[parts.length -1];
		}
		if(methodName == null){
			methodName = "index";
			log.debug("use bean name:" + beanName +"  default method name:"+methodName);
		}else{
			log.debug("use bean name:" + beanName +"  method name:"+methodName);
		}
		return new String[]{beanName,methodName};
	}

	public synchronized ApplicationContext getApplicationContext() {
		if(appContext == null){
			appContext =WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		}
		assert(appContext != null);
		return appContext;
	}
	
	@Override
	public void init(ServletConfig config) throws ServletException{
		super.init(config);
		init();
	}
	
	public void init() throws ServletException {
		try {
			velocityEngine = new VelocityConfigurer(getServletContext()).createVelocityEngine(); 
		} catch (Exception e) {
		  throw new ServletException(e);
		}
	}
	
	private void renderVm(HttpServletResponse httpResponse,String templateName,String encoding) {
		Context context = null;//new ChainedContext();
		Template temp = getTemplate(templateName, encoding);
		megerTemplate(httpResponse,temp,context);
	}
	
	protected void render(HttpServletResponse response,String templateName,String encoding){
		Context context = null;//new ChainedContext();
		String contextAsString = getTemplateAsString(context, getTemplate(templateName, DEFAULT_ENCODE));
		
		String layoutName = getLayoutName(templateName);
		Template layoutTemplate = getTemplateAndWrap(encoding, layoutName);
		String layoutContextAsString = contextAsString;
		if(layoutTemplate != null){
			Context layoutContext = null;//new LayoutContext(contextAsString);
			layoutContextAsString = getTemplateAsString(layoutContext, layoutTemplate);
		}
		onceWrite(response, layoutContextAsString);					 
	}


	private void onceWrite(HttpServletResponse response,
			String layoutContextAsString) {
		response.addHeader("Content-Length", String.format("%s", new Object[]{layoutContextAsString.length()}));
		response.addHeader("Power-By", "XS");
		Writer write = null;
		try {
			write = response.getWriter();
			write.write(layoutContextAsString);
		} catch (Exception e) {
		}finally{
			if(write != null){
				try {
					write.flush();
				} catch (IOException e) {
				}
			}
		}
	}



	private Template getTemplateAndWrap(String encoding, String layoutName) {
		try {
			return  getTemplate(layoutName, encoding);
		} catch (Exception e) {
			 
		}
		return null;
	}

	public String getTemplateAsString(Context context,Template template){
		StringWriter swrite = new StringWriter(1024);
		VelocityWriter vw = new VelocityWriter(swrite, 4 *1024, true);
		try {
			template.merge(context, vw);
			vw.flush();
			return swrite.toString();
		} catch (Exception e) {
			throw new IllegalArgumentException(e);
		}finally{
			try {
				vw.close();
			} catch (IOException e) {
			}
		}
	}

	private String getLayoutName(String templateName) {
		String[] templateNameParts = templateName.split("/");
		StringBuilder sb = new StringBuilder();
		for(int i=0 ; i< templateNameParts.length -1; i++){
			sb.append(templateNameParts[i]).append("/");
		}
		sb.append("_").append(templateNameParts[templateNameParts.length -1]);
		return sb.toString();
	}
	
	 
	 protected void megerTemplate(HttpServletResponse response,Template template,Context context) {
		 VelocityWriter vw = null;
		 Writer writer  = null;
		 try {
			 StringWriter swrite = new StringWriter(1024);
			 writer = getResponseWriter(response);
			 vw = new VelocityWriter(swrite, 4 *1024, true);
			 template.merge(context, vw);
			 if(vw != null){
					try {
						vw.flush();
					} catch (IOException e) {
						 throw new VelocityException(e);
					}finally{
						vw.recycle(null);
					}
					 
				}
			swrite.close();
			writer.write(swrite.toString());
			
		} catch (Exception e) {
			throw new RuntimeException(e);
		}finally{
			if(writer != null){
				try {
					writer.flush();
				} catch (IOException e) {
					 
				}
			}
		}
		
		 
	 }

	public Template getTemplate(String name, String encoding){
    try {
    	if (encoding == null){
			return getVelocityEngine().getTemplate(name);
     }
     else
     {  
			return getVelocityEngine().getTemplate(name, encoding); 
     }
	} catch (Exception e) {
//		throw new VelocityException(e);
		throw new TemplateNotFoundException();
	}
	 
   }

	 
	  protected Writer getResponseWriter(HttpServletResponse response)
      throws UnsupportedEncodingException, IOException
  {
      Writer writer = null;
      try
      {
          writer = response.getWriter();
      }
      catch (IllegalStateException e)
      {
          
          String encoding = response.getCharacterEncoding();
          if (encoding == null)
          {
              encoding = DEFAULT_ENCODE;
          }
          writer = new OutputStreamWriter(response.getOutputStream(),
                                          encoding);
      }
      return writer;
  }
	  
	private VelocityEngine getVelocityEngine() {
		return velocityEngine;
	}
}
